package org.om.core.impl.persistence.jcr; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Value; import org.om.core.api.annotation.CollectionMode; import org.om.core.api.exception.ObjectMapperException; import org.om.core.api.exception.PersistenceLayerException; import org.om.core.api.mapping.CollectionMapping; import org.om.core.api.mapping.EntityMapping; import org.om.core.api.mapping.MappedField; import org.om.core.api.mapping.field.PropertyMapping; import org.om.core.api.persistence.PersistenceAdapter; import org.om.core.api.persistence.request.Mode; import org.om.core.api.persistence.request.PersistenceRequest; import org.om.core.api.persistence.result.CollectionResult; import org.om.core.api.persistence.result.MapResult; import org.om.core.api.persistence.result.PersistenceResult; import org.om.core.impl.persistence.result.ImmutableCollectionPersistenceResult; import org.om.core.impl.persistence.result.ImmutablePersistenceResult; import org.om.core.impl.persistence.result.MissingCollectionPersistenceResult; import org.om.core.impl.persistence.result.MissingPersistenceResult; import org.om.core.impl.persistence.result.NoValuePersistenceResult; import org.om.core.impl.persistence.result.map.ExceptionThrowingMapResult; import org.om.core.impl.persistence.result.map.ImmutableMapResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Jakob Külzer * @author tome * */ public class JcrPersistenceAdapter implements PersistenceAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(JcrPersistenceAdapter.class); /** * The entity mapping for the entity that maps 1:1 to this node */ private final EntityMapping entityMapping; /** * The node that backs all persistence operations on the enclosing entity. */ private final Node node; private final String id; public JcrPersistenceAdapter(EntityMapping entityMapping, Node node) { this.node = node; this.entityMapping = entityMapping; try { id = node.getPath(); } catch (RepositoryException e) { throw new ObjectMapperException("Could not get path from Node."); } LOGGER.trace("New JcrPersistenceDelegate for {} with {}", node, entityMapping); } public void delete() throws ObjectMapperException { // Disabled for now. Transaction semantics need to be defined. } public CollectionResult getCollection(CollectionMapping collectionMapping) { try { final List<String> paths = new LinkedList<String>(); final String location = collectionMapping.getLocation(); // TODO: This is a bit of a hack right now. We'll need a better // representation of how to retrieve elements for collections. if (node.hasNode(location)) { final Node collectionContainer = node.getNode(location); LOGGER.debug("Retrieving from node {}", collectionContainer.getPath()); for (NodeIterator ni = collectionContainer.getNodes(); ni.hasNext();) { final Node child = ni.nextNode(); // TODO: This is a quick'n'dirty fix to avoid having // jcr:content node in node level collections. I can't think // of a use case where this would be required, yet I think // this should be configurable in some way. if (child.getName().equals("jcr:content")) continue; LOGGER.trace("Adding {} to collection.", child.getPath()); paths.add(child.getPath()); } } else if (node.hasProperty(location) && node.getProperty(location).isMultiple()) { final Property property = node.getProperty(location); LOGGER.debug("Retrieving from property {}", property.getPath()); for (Value v : property.getValues()) { paths.add(v.getString()); } } else { return new MissingCollectionPersistenceResult(); } return new ImmutableCollectionPersistenceResult(paths); } catch (RepositoryException e) { throw new ObjectMapperException("Could not retrieve collection from " + collectionMapping.getLocation()); } } @Override public PersistenceResult getProperty(PersistenceRequest request) { try { Node node = this.node; if (request.getMode() == Mode.Absolute) throw new UnsupportedOperationException("Implement absolute node retrieval!"); if (!node.hasProperty(request.getPath())) return new NoValuePersistenceResult(); final Property property = node.getProperty(request.getPath()); if (property.isMultiple()) throw new UnsupportedOperationException("Implement support for multiple valued properties!"); final Class<?> expectedType = request.getExpectedType(); Object value; // The following is probably not necessary, but should save us the // overhead of going through conversion via property editors on the // interceptor level. if (int.class == expectedType || Integer.class == expectedType || long.class == expectedType || Long.class == expectedType) { value = property.getLong(); } else if (float.class == expectedType || Float.class == expectedType || double.class == expectedType || Double.class == expectedType) { value = property.getDouble(); } else if (boolean.class == expectedType || Boolean.class == expectedType) { value = property.getBoolean(); } else { value = property.getString(); } return new ImmutablePersistenceResult(value); } catch (RepositoryException e) { throw new PersistenceLayerException("Exception while retrieving " + request, e); } } public PersistenceResult getProperty(PropertyMapping propertyMapping) { // TODO: should check if the given mapping actually exists in // entityMapping. // TODO: The missing handling should be moved into the parent: final String propertyName = propertyMapping.getPropertyName(); try { if (!node.hasProperty(propertyName)) { final MappedField mappedField = entityMapping.getMappedFields().getFieldByMapping(propertyMapping); return MissingPersistenceResult.createMissing(mappedField); } final Property property = node.getProperty(propertyName); if (property.isMultiple()) { throw new RuntimeException("Cannot handle multi-value properties yet."); } // TODO: this could be more efficient for binary types: return new ImmutablePersistenceResult(property.getValue().getString()); } catch (final RepositoryException e) { throw new ObjectMapperException("Exception in getProperty " + propertyName, e); } } @Override public Object resolve(String path) { if (path == null) throw new NullPointerException("path is null"); boolean isAbsolutePath = path.startsWith("/"); // If the path is absolute, no need to resolve it. if (isAbsolutePath) { return path; } try { return node.getPath() + "/" + path; } catch (RepositoryException e) { throw new PersistenceLayerException("Exception while resolving path.", e); } } public void setProperty(PropertyMapping propertyMapping, Object object) throws ObjectMapperException { // Disabled as this assumes write-through semantics which doesn't allow // rollback. // final String propertyName = propertyMapping.getPropertyName(); // try { // Class<?> propertyType = propertyMapping.getFieldType(); // if (propertyType == String.class) { // node.setProperty(propertyName, (String) object); // } else if (propertyType == int.class) { // node.setProperty(propertyName, ((Integer) object).intValue()); // } else if (propertyType == Integer.class) { // node.setProperty(propertyName, (Integer) object); // } else { // throw new ObjectMapperException("Unknown property type"); // } // // } catch (final RepositoryException e) { // throw new ObjectMapperException("Exception in setProperty " + // propertyName, e); // } } @Override public String getId() { return id; } @Override public MapResult getMapResult(CollectionMapping collectionMapping) { LOGGER.trace("Retrieving map using {}", collectionMapping); Map<Object, Object> result = Collections.EMPTY_MAP; try { Node baseNode = node.getNode(collectionMapping.getLocation()); LOGGER.trace("Base node {}", baseNode.getPath()); if (collectionMapping.getCollectionMode() == CollectionMode.Properties) { result = new LinkedHashMap<Object, Object>(); int i = 0; for (PropertyIterator pi = baseNode.getProperties(); pi.hasNext();) { final Property property = pi.nextProperty(); final String propertyName = property.getName(); if (propertyName.startsWith("jcr:")) continue; String key; switch (collectionMapping.getMapKeyStrategy()) { case Index: key = Integer.toString(i); break; default: case Name: key = propertyName; break; } String value = property.getString(); result.put(key, value); i++; } } else { throw new ObjectMapperException("Don't know how to deal with collection mode " + collectionMapping.getCollectionMode() + " yet."); } } catch (PathNotFoundException e) { return new ExceptionThrowingMapResult(); } catch (RepositoryException e) { throw new PersistenceLayerException("Exception while retrieving " + collectionMapping, e); } return new ImmutableMapResult(result); } }